package date_202403

import (
	"fmt"
	"math"
)

//1140. 石子游戏 II
//
//爱丽丝和鲍勃继续他们的石子游戏。许多堆石子 排成一行，每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
//
//爱丽丝和鲍勃轮流进行，爱丽丝先开始。最初，M = 1。
//
//在每个玩家的回合中，该玩家可以拿走剩下的 前 X 堆的所有石子，其中 1 <= X <= 2M。然后，令 M = max(M, X)。
//
//游戏一直持续到所有石子都被拿走。
//
//假设爱丽丝和鲍勃都发挥出最佳水平，返回爱丽丝可以得到的最大数量的石头。
//
//示例 1：
//
//输入：piles = [2,7,9,4,4]
//输出：10
//解释：如果一开始Alice取了一堆，Bob取了两堆，然后Alice再取两堆。爱丽丝可以得到2 + 4 + 4 = 10堆。如果Alice一开始拿走了两堆，那么Bob可以拿走剩下的三堆。在这种情况下，Alice得到2 + 7 = 9堆。返回10，因为它更大。
//示例 2:
//
//输入：piles = [1,2,3,4,5,100]
//输出：104
//
//提示：
//1 <= piles.length <= 100
//1 <= piles[i] <= 104

var maxStoneCache map[string]int
var sumCache map[int]int

func stoneGameII(piles []int) int {

	sumCache = make(map[int]int, len(piles))
	sumCache[len(piles)-1] = piles[len(piles)-1]
	for i := 1; i < len(piles); i++ {
		sumCache[len(piles)-i-1] = sumCache[len(piles)-i] + piles[len(piles)-i-1]
	}
	maxStoneCache = make(map[string]int, len(piles)*len(piles))

	return getMaxStone(piles, 0, 1)
}

// 指定M值和开始的Idx(包含)，返回当前能拿到的最多石头
func getMaxStone(piles []int, beginIdx int, mVal int) int {

	cacheKey := fmt.Sprintf("%d_%d", beginIdx, mVal)
	if cacheVal, exist := maxStoneCache[cacheKey]; exist {
		return cacheVal
	}

	// 如果能拿完，直接拿完
	if beginIdx+2*mVal >= len(piles) {
		maxVal := 0
		for ; beginIdx < len(piles); beginIdx++ {
			maxVal = piles[beginIdx] + maxVal
		}
		maxStoneCache[cacheKey] = maxVal
		return maxVal
	}

	// 计算那种方式下对手拿到的最少
	targetMinVal := math.MaxInt
	for i := 1; i <= 2*mVal; i++ {
		nextM := IntMax9(mVal, i)
		targetMaxStone := getMaxStone(piles, beginIdx+i, nextM)
		if targetMaxStone < targetMinVal {
			targetMinVal = targetMaxStone
		}
	}

	maxVal := sumCache[beginIdx] - targetMinVal
	maxStoneCache[cacheKey] = maxVal
	return maxVal
}

func IntMax9(a, b int) int {
	if a > b {
		return a
	}
	return b
}
